package oop.ex2.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Sjavac {

    //the array for the method and the global variables
    public static ArrayList<Method> mainMethods = new ArrayList<Method>();
    public static ArrayList<Variable> mainVariables = new ArrayList<Variable>();

    public static void main(String[] args) {

        try {
            File file = new File(args[0]);
            //clear all the comments and get all declared methods and variables. After that, collect
            //all the method block
            ArrayList<String> code = getStringFromFile(file);
            // scan the rest of the code - all the method blocks and remove each line after handling
            while (!code.isEmpty()) {
                // method declaration - a start of a method block
                if (code.get(0).matches(Patterns.METHOD_DECLARATION)) {
                    boolean inMethod = true;
                    //create a new method object
                    Method method = new Method(code.get(0), mainMethods, mainVariables, null,null);
                    //check each line individually
                    while (inMethod) {
                        method.checkMethod(code.get(0));
                        // if there is an if/while condition, check it individually, and remember the
                        //method it came from
                        if (code.get(0).matches(Patterns.IF_WHILE_PATTERN)) {
                            splitCondition(code, method);
                            continue;
                        }
                        //stop in the end of the method block
                        else if (code.get(0).trim().matches("}")) {
                            code.remove(0);
                            inMethod = false;
                            break;

                        }
                        code.remove(0);
                    }

                }
                // method call
//                else if (code.get(0).matches(Patterns.METHOD_CALL)) {
//                    Method method = new Method(code.get(0), mainMethods, mainVariables);
//                }
                else {
                    throw new compileException("line: "+"<" + code.get(0) +">, illegal method line");
                }
            }
            //if there are no errors and the end of the program is reached, the code compiles!
            System.out.println("CODE COMPILES");
            System.exit(0);
            //throw exception in case of errors
        } catch (compileException e) {
            e.printStackTrace();
            System.exit(1);
        } catch (VariableException e) {
            e.printStackTrace();
            System.exit(1);
        } catch (MethodException e) {
            e.printStackTrace();
            System.exit(1);
        } catch (ConditionException e) {
            e.printStackTrace();
            System.exit(1);
            //IO exceptions
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(2);
        }

    }

    /**
     * This methid splits a condition block from a method block, every if/while related line is 
     * splittet and handled by the Conditional object
     * @param code - the array of lines
     * @param method - the original method
     * @throws compileException
     * @throws VariableException
     * @throws MethodException
     * @throws ConditionException
     */
    public static void splitCondition(ArrayList<String> code, Method method) throws compileException,
            VariableException, MethodException, ConditionException {
        //count the block
        int conditionCount = 1;
        //the lines for the conditional
        ArrayList<String> conditionLines = new ArrayList<String>();
        conditionLines.add(code.get(0));
        code.remove(0);
        //add all the related lines
        while (conditionCount != 0) {
            if (code.get(0).trim().matches(Patterns.IF_WHILE_PATTERN)) {
                conditionCount++;
                conditionLines.add(code.get(0));
                code.remove(0);
            }
            else if (code.get(0).trim().matches("}")) {
                conditionCount--;
                conditionLines.add(code.get(0));
                code.remove(0);
            }
            else {
                conditionLines.add(code.get(0));
                code.remove(0);
            }

        }
        //create a new conditional object which will check the whole block
        Conditional conditional = new Conditional(conditionLines, mainVariables, mainMethods, method);
    }


    /**
     * Updates a variable's value
     * 
     * @param localVariables
     * 
     * @param variable
     *            the variable to update
     * @param line
     *            - the assign line for the variable
     * @throws compileException
     */
    public static void updateVariable(ArrayList<Variable> varArray, ArrayList<Variable> arguments, String line)
            throws compileException {
        boolean changed = false;
        // the pattern and matcher for the regx
        Pattern pattern = Pattern.compile(Patterns.VARIABLE_ASSAINMENT);
        Matcher matcher = pattern.matcher(line);
        // if the expression is a variable declaration
        if (matcher.matches()) {
            // find the variable in the list of variables
            for (Variable variable : varArray) {
                // get the type for comparison
                VariableType type = VariableType.valueOf(variable.getType().toUpperCase());
                // if the name matches the assign name
                if (variable.getName().trim().equals(matcher.group(1).trim())) {
                    // if the type matches the value
                    if (matcher.group(2).matches(type.getRegex())) {
                        if (variable.isFinal()) {
                            throw new compileException("cannot assign final variable");
                        }
                        variable.setVariable(matcher.group(2));
                        changed = true;
                        System.out.println("assained! variable: " + variable.getName() + ", value: "
                                + variable.getValue());
                        break;
                    }
                    else {
                        // throw exception in case of mismatch name
                        throw new compileException("cannot assign Variable: " + variable.getName()
                                + ", mismatch to the variable type");
                    }
                }

            }
            if (arguments != null) {
                for (Variable variable : arguments) {
                    // get the type for comparison
                    VariableType type = VariableType.valueOf(variable.getType().toUpperCase());
                    if (variable.getName().trim().equals(matcher.group(1).trim())) {
                        // if the type matches the value
                        if (matcher.group(2).matches(type.getRegex())) {
                            variable.setVariable(matcher.group(2));
                            changed = true;
                        }
                    }
                }

            }
            // throw exception in case of the variable isn't yet
            // declared
            if (!changed)
                throw new compileException("cannot assign variable, variabe: " + line + " not yet declred");

        }
        else {
            // throw exception if the line isn't an assignment
            throw new compileException("The line: " + line + "- mismatch to variable set");
        }
    }

    /**
     * Collect a string representation of the Code file.
     * 
     * @param file
     *            - the command file to read the text from
     * @return the string representation of the command file.
     * @throws IOException
     * @throws compileException
     * @throws MethodException
     * @throws VariableException
     */
    public static ArrayList<String> getStringFromFile(File file) throws IOException, compileException,
            VariableException, MethodException {
        ArrayList<String> array = new ArrayList<String>();
        boolean inMethod = false;
        // use buffer for efficiency
        BufferedReader input = new BufferedReader(new FileReader(file));
        String line = null;
        // read line by line
        boolean inComment = false;
        ArrayList<String> variables = new ArrayList<String>();
        while ((line = input.readLine()) != null) {

            // append a single line
            line = line.trim();
            //for a start of a comment block
            if (line.startsWith("/*") || line.startsWith("/**")) {
                inComment = true;
                continue;
            }
            //for a line contains an "//" comment, remobe the comment from the line
            if (line.indexOf(";") < line.length() && line.contains("//")) {
                line = line.substring(0, line.indexOf(";") + 1);
            }
            //for a single line comment
            if (line.contains("/*") && line.contains("*/")) {
                line = line.replaceAll("/\\*.*\\*/", " ");
            }
            //for the end if a comment block
            if (line.endsWith("*/")) {
                inComment = false;
                continue;
            }
            //continue scanning while the comment block is not empty
            if (inComment) {
                continue;
            }
            //for an empty line
            if (line.isEmpty()) {
                continue;
            }
            //for a method block, continue for second scanning
            if (inMethod) {
                line = line.trim();
                if (line.matches("}")) {
                    inMethod = false;
                    array.add(line);
                    continue;
                }
                else {
                    array.add(line);
                    continue;
                }
            }
            // variable declaration
            if (line.matches(Patterns.VARIABLE_DECLARATION) || line.matches(Patterns.VARIABLE_SHORT_DECLARATION)) {
                variables.add(line);

            }
            //insert the methods titles to the array
            else if (line.trim().matches(Patterns.METHOD_DECLARATION)) {
                System.out.println(1);
                array.add(line);
                Method method = new Method(line, mainMethods, mainVariables,null,null);
                //for a double declaration
                if (method.contains(mainMethods)) {

                    throw new compileException("Method: " + line + ", already declared");
                }
                else {
                    mainMethods.add(method);
                    inMethod = true;
                }

            }

            else {
                array.add(line);

            }

        }
        //insert the global variables to the array
        for (String varLine : variables) {
            Variable variable = new Variable(varLine, mainVariables, mainMethods, null);
            if (!variable.exists(mainVariables)) {
                mainVariables.add(variable);
            }
            else {
                throw new compileException("variable: <" + variable.getName() + "> already exists");
            }
        }
        input.close();
        // returns the string of the command file

        return array;

    }
}
